﻿//A core util class to bring Draggable behavior to any DOM element, acts as a base class for Scroller and Sortable
Ext.util.Draggable = Ext.extend(Ext.util.Observable, {
    baseCls: 'x-draggable',//拖拽节点的样式类
    draggingCls: 'x-dragging',
    proxyCls: 'x-draggable-proxy',//代理拖拽节点的样式类
    outOfBoundRestrictFactor: 1,//越界偏移比例
    direction: 'both',//可拖拽方向
    fps: Ext.is.Blackberry ? 25 : ((Ext.is.iOS || Ext.is.Desktop) ? 80 : 50),//动画频率
    constrain: window,//拖拽节点的容器节点
    threshold: 0,
    delay: 0,//延时多少毫秒才开始响应拖拽动作

    //A simple CSS selector that represents elements within the draggable that should NOT initiate a drag.
    cancelSelector: null,
    disabled: false,//是否禁用拖拽功能
    revert: false,//决定如果拖拽节点没有到达Dropable节点范围，拖拽节点是否回到开始位置

    //Draggable and Droppable objects can participate in(参与) a group which are capable(能力) of interacting(交互). Defaults to 'base'
    group: 'base',
    useCssTransform: true,//是否使用css3的transform来代替left和top样式控制（更快）


    grid: null,
    snap: null,
    proxy: null,//代理节点
    stack: false,
    animationDuration: 300,//拖拽动画的持续时间

    //是否在开始拖拽时更新边界信息（即如果拖拽节点超出容器范围之外，在最小限度上使其移到容器范围之内，参数表示重新计算偏移量）
    updateBoundaryOnTouchStart: true,


    //Read-only Property representing the region that the Draggable is constrained(约束) to.
    offsetBoundary: null,

    //Read-only Property representing whether or not the Draggable is currently dragging or not.
    dragging: false,
    vertical: false,//是否允许纵向拖拽
    horizontal: false,//是否允许横向拖拽


    monitorOrientation: true,

    constructor: function (el, config) {
        this.el = Ext.get(el);//获取拖拽的节点
        this.id = el.id;//节点标识

        config = config || {};

        Ext.apply(this, config);//给拖拽类混入配置对象的属性

		//注册事件
        this.addEvents(
            'offsetchange',
            'offsetboundaryupdate'
        );

        Ext.util.Draggable.superclass.constructor.call(this, config);

        //设置实际触发拖动事件的元素
		//如果配置对象混入的eventTarget属性为'parent'
        if (this.eventTarget === 'parent') {
            this.eventTarget = this.el.parent();//则eventTarget为拖拽节点的父节点
        } else {
			//否则eventTarget为拖拽节点本身
            this.eventTarget = (this.eventTarget) ? Ext.get(this.eventTarget) : this.el;
        }

        //设置拖动方向
        if (this.direction == 'both') {
            this.horizontal = true;
            this.vertical = true;
        }
        else if (this.direction == 'horizontal') {
            this.horizontal = true;
        }
        else {
            this.vertical = true;
        }

		//给拖拽节点设置样式
        this.el.addCls(this.baseCls);

		//如果指定了代理拖拽节点
        if (this.proxy) {
			//返回配置对象中混入的代理拖拽节点，如果未指定，则代理拖拽节点就是节点本身
            this.getProxyEl().addCls(this.proxyCls); //然后添加样式
        }

        //开始事件名(有延时则为'taphold'，直接响应则为'dragstart')
        this.startEventName = (this.delay > 0) ? 'taphold' : 'dragstart';


        //拖动的一些数据
        this.dragOptions = (this.delay > 0) ? { holdThreshold: this.delay} : {
            direction: this.direction,
            dragThreshold: this.threshold
        };

        this.container = window;//拖拽类的容器节点为window

        //constrain : Element/Mixed
        //Can be either a DOM element, an instance of Ext.Element, 'parent' or null for no constrain
		//配置对象混入的容器节点
        if (this.constrain) {
			//如果容器节点指定为拖拽节点的父节点，则this.container指向父节点
            if (this.constrain === 'parent') {
                this.container = this.el.parent();
            }
			//否则如果指定的容器节点不是窗口，则this.container指向指定节点
            else if (this.constrain !== window) {
                this.container = Ext.get(this.constrain);
            }
        }

        //偏移坐标对象
        this.offset = new Ext.util.Offset();

		//拖拽的直线动画对象
        this.linearAnimation = {
            x: new Ext.util.Draggable.Animation.Linear(),//拖拽对象线型方法对象，主要有getOffset方法
            y: new Ext.util.Draggable.Animation.Linear()
        };

		//更新边界信息（即如果拖拽节点超出容器范围之外，在最小限度上使其移到容器范围之内，参数表示重新计算偏移量）
        this.updateBoundary(true);

        this.setDragging(false);//将拖动的状态、样式设为不拖动

		//如果没有禁用拖拽功能
        if (!this.disabled) {
            this.enable();//启用拖拽功能（即注册监听函数）
        }

        return this;
    },

    //启动拖动功能
    enable: function () {
        return this.setEnabled(true);//添加各拖动事件的侦听函数
    },

    //禁用拖动功能
    disable: function () {
        return this.setEnabled(false);//移除个拖动事件的侦听函数
    },

	//设置拖拽功能的启用禁用
    setEnabled: function (enabled) {
	
		//注册或删除监听函数
		/*
		//开始事件名(有延时则为'taphold'，直接响应则为'dragstart')
        this.startEventName = (this.delay > 0) ? 'taphold' : 'dragstart';
		*/
        this.eventTarget[enabled ? 'on' : 'un'](this.startEventName, this.onStart, this, this.dragOptions);
        this.eventTarget[enabled ? 'on' : 'un']('drag', this.onDrag, this, this.dragOptions);
        this.eventTarget[enabled ? 'on' : 'un']('dragend', this.onDragEnd, this, this.dragOptions);
        this.eventTarget[enabled ? 'on' : 'un']('touchstart', this.onTouchStart, this);

		//如果启用拖拽功能
        if (enabled) {
            Ext.EventManager.onOrientationChange(this.onOrientationChange, this);//给拖拽对象注册转屏事件
        } else {
            Ext.EventManager.orientationEvent.removeListener(this.onOrientationChange, this);
        }

        this.disabled = !enabled;//设置禁用状态标准布尔值

        return this;
    },

    //Change the Draggable to use css transforms instead of style offsets or the other way around.
    //useCssTransform :    True to use css transforms instead of style offsets.
    setUseCssTransform: function (useCssTransform) {
        if (typeof useCssTransform == 'undefined') {
            useCssTransform = true;
        }

        if (useCssTransform != this.useCssTransform) {
            this.useCssTransform = useCssTransform;

            var resetOffset = new Ext.util.Offset();

            if (useCssTransform == false) {
                this.setStyleOffset(this.offset);
                this.setTransformOffset(resetOffset, true);
            } else {
                this.setTransformOffset(this.offset);
                this.setStyleOffset(resetOffset);
            }
        }

        return this;
    },

    //根据偏移量设置拖拽节点坐标
    setOffset: function (offset, animate) {
		//如果不支持横坐标拖拽
        if (!this.horizontal) {
            offset.x = 0;//横向偏移量为0
        }

		//如果不支持纵坐标拖拽
        if (!this.vertical) {
            offset.y = 0;//纵向偏移量为0
        }

		//如果offset不是一个Ext.util.Offset对象，创建一个Ext.util.Offset对象
        if (!(offset instanceof Ext.util.Offset)) {
            offset = Ext.util.Offset.fromObject(offset);
        }

        offset.round();//四舍五入

		//如果参数偏移量与当前偏移量不同
        if (!this.offset.equals(offset)) {
			//如果需要动画效果
            if (animate) {
				//运行动画
                this.startAnimation(offset, animate);
            }
            else {
                this.offset = offset;
				//终点区域对象
                this.region = new Ext.util.Region(
                    this.initialRegion.top + offset.y,
                    this.initialRegion.right + offset.x,
                    this.initialRegion.bottom + offset.y,
                    this.initialRegion.left + offset.x
                );

				//如果使用css transform代替left和top控制
                if (this.useCssTransform) {
                    this.setTransformOffset(offset);
                }
                else {
					//left和top控制代理节点位置
                    this.setStyleOffset(offset);
                }

                this.fireEvent('offsetchange', this, this.offset);
            }
        }


        return this;
    },

	//css transform控制代理节点位置
    setTransformOffset: function (offset, clean) {

        if (clean) {
            this.getProxyEl().dom.style.webkitTransform = '';
        } else {
            Ext.Element.cssTranslate(this.getProxyEl(), offset);//将代理节点移到指定位置
        }

        return this;
    },

	//left和top控制代理节点位置
    setStyleOffset: function (offset) {
        var el = this.getProxyEl();

        el.dom.style.left = offset.x + 'px';
        el.dom.style.top = offset.y + 'px';

        return this;
    },

	//运行移动动画
    startAnimation: function (offset, animate) {
        var me = this;

        this.stopAnimation();//停止先前动画

        var currentTime = Date.now();//当前时间
		
        animate = Ext.isNumber(animate) ? animate : this.animationDuration;//动画持续时间

		//横坐标动画类设置参数
        this.linearAnimation.x.set({
            startOffset: this.offset.x,
            endOffset: offset.x,//目标偏移量
            startTime: currentTime,//开始运行动画的时间
            duration: animate//动画持续时间
        });

		//纵坐标动画类设置参数
        this.linearAnimation.y.set({
            startOffset: this.offset.y,
            endOffset: offset.y,
            startTime: currentTime,
            duration: animate
        });

        this.isAnimating = true;//动画进行时标志

        this.animationTimer = setInterval(function () {
            me.handleAnimationFrame();//设置当前时间断下拖拽节点的位置
        }, this.getFrameDuration());
        return this;
    },

	//获取动画延迟
    getFrameDuration: function () {
        return 1000 / this.fps;
    },

	//停止动画
    stopAnimation: function () {
		//如果动画正在执行
        if (this.isAnimating) {
            clearInterval(this.animationTimer);//清除动画定时器
            this.isAnimating = false;//动画执行结束
            this.setDragging(false);//拖拽进行标志设为false
        }

        return this;
    },

	//设置当前时间断下拖拽节点的位置
    handleAnimationFrame: function () {
        if (!this.isAnimating) {//如果不在动画进行时，直接返回
            return;
        }

        var newOffset = new Ext.util.Offset();//动画分解的偏移量
        newOffset.x = this.linearAnimation.x.getOffset();//获取当前时间断下横坐标的偏移量
        newOffset.y = this.linearAnimation.y.getOffset();//获取当前时间断下纵坐标的偏移量

        this.setOffset(newOffset);//给拖拽节点定位

		//如果横坐标与纵坐标的偏移距离都达到了指定距离
        if ((newOffset.x === this.linearAnimation.x.endOffset) && (newOffset.y === this.linearAnimation.y.endOffset)) {
            this.stopAnimation();//结束动画
        }
    },

    //Returns the current offset relative to the original location of this Draggable.
    getOffset: function () {
        var offset = this.offset.copy();
        offset.y = -offset.y;
        offset.x = -offset.x;
        return offset;
    },

	//更新边界信息（即如果拖拽节点超出容器范围之外，在最小限度上使其移到容器范围之内，init参数表示重新计算偏移量）
    updateBoundary: function (init) {
        var offsetBoundary;

        if (typeof init == 'undefined')
            init = false;

		//拖拽节点的尺寸
        this.size = {
            width: this.el.dom.scrollWidth,
            height: this.el.dom.scrollHeight
        };

		//如果容器节点是window
        if (this.container === window) {
			//边界框为window的范围
            this.containerBox = {
                left: 0,
                top: 0,
                right: this.container.innerWidth,
                bottom: this.container.innerHeight,
                width: this.container.innerWidth,
                height: this.container.innerHeight
            };
        }
		//否则获取边界节点的范围
        else {
            this.containerBox = this.container.getPageBox();
        }

		//拖拽节点当前的位置
        var elXY = this.el.getXY();

		//拖拽节点的占地范围
        this.elBox = {
            left: elXY[0] - this.offset.x,
            top: elXY[1] - this.offset.y,
            width: this.size.width,
            height: this.size.height
        };

        this.elBox.bottom = this.elBox.top + this.elBox.height;
        this.elBox.right = this.elBox.left + this.elBox.width;

		//区域对象，该对象主要封装了一些方法供调用
        this.initialRegion = this.region = new Ext.util.Region(
            elXY[1], elXY[0] + this.elBox.width, elXY[1] + this.elBox.height, elXY[0]
        );

        var top = 0,
            right = 0,
            bottom = 0,
            left = 0;

		//如果拖拽节点的左边界超出容器节点的左边界
        if (this.elBox.left < this.containerBox.left) {
            right += this.containerBox.left - this.elBox.left;//拖拽节点应该向右偏移的量值
        }
        else {
            left -= this.elBox.left - this.containerBox.left;//拖拽节点应该向左偏移的量值
        }

		//如果拖拽节点的右边界超出容器节点的右边界
        if (this.elBox.right > this.containerBox.right) {
            left -= this.elBox.right - this.containerBox.right;//拖拽节点应该向左偏移的量值
        }
        else {
            right += this.containerBox.right - this.elBox.right;//拖拽节点应该向右偏移的量值
        }

		//如果拖拽节点的上边界超出容器节点的上边界
        if (this.elBox.top < this.containerBox.top) {
            bottom += this.containerBox.top - this.elBox.top;//拖拽节点应该向下偏移的量值
        }
        else {
            top -= this.elBox.top - this.containerBox.top;//拖拽节点应该向上偏移的量值
        }

		//如果拖拽节点的下边界超出容器节点的下边界
        if (this.elBox.bottom > this.containerBox.bottom) {
            top -= this.elBox.bottom - this.containerBox.bottom;//拖拽节点应该向上偏移的量值
        }
        else {
            bottom += this.containerBox.bottom - this.elBox.bottom;//拖拽节点应该向下偏移的量值
        }

		//偏移区域的四舍五入值
        offsetBoundary = new Ext.util.Region(top, right, bottom, left).round();

        if (this.offsetBoundary && this.offsetBoundary.equals(offsetBoundary)) {
            return this;
        }

		//给拖拽对象一个属性指向偏移对象对象
        this.offsetBoundary = offsetBoundary;

		//触发偏移更新事件
        this.fireEvent('offsetboundaryupdate', this, this.offsetBoundary);

        var currentComputedOffset;

		//如果使用css的transform来代替left和top样式控制（更快）
        if (this.useCssTransform) {
			//根据代理节点计算css 2d的偏移量（一个Ext.util.Offset对象）
            currentComputedOffset = Ext.Element.getComputedTransformOffset(this.getProxyEl());

			//如果之前没有设置过此偏移量或参数强制重新设置此偏移量
            if (!this.offset.equals(currentComputedOffset) || init) {
                this.setOffset(currentComputedOffset);//给拖拽节点定位
            }
        }

        return this;
    },


    onTouchStart: function () {

    },

	//手指按下延时拖拽到开始事件
    onStart: function (e) {
		//如果手指按下时更新边界信息
        if (this.updateBoundaryOnTouchStart) {
			//更新边界信息（即如果拖拽节点超出容器范围之外，在最小限度上使其移到容器范围之内，参数表示重新计算偏移量）
            this.updateBoundary();
        }

        this.stopAnimation();//停止先前的动画

        this.setDragging(true);//拖拽进行标识重新设为true
		
		//开始时刻坐标
        this.startTouchPoint = new Ext.util.Point(e.startX, e.startY);

        this.startOffset = this.offset.copy();//开始点的坐标对象

        this.fireEvent('dragstart', this, e);//触发拖拽开始事件

        return true;
    },

	//获取标志化的新偏移坐标对象（即有时候单纯支持横向拖拽或纵向拖拽时只改变其中一个坐标）
    getNewOffsetFromTouchPoint: function (touchPoint) {
        var xDelta = touchPoint.x - this.startTouchPoint.x,//当前坐标与开始坐标的横坐标之差
            yDelta = touchPoint.y - this.startTouchPoint.y,//当前坐标与开始坐标的纵坐标之差
            newOffset = this.offset.copy();//开始坐标对象

		//如果当前点与开始点坐标一致，直接返回开始点
        if (xDelta == 0 && yDelta == 0) {
            return newOffset;
        }

		//如果支持横向拖拽
        if (this.horizontal)
            newOffset.x = this.startOffset.x + xDelta;//更新横向坐标

		//如果支持纵向拖拽
        if (this.vertical)
            newOffset.y = this.startOffset.y + yDelta;//更新纵向坐标

        return newOffset;
    },

	//拖拽进行时的相应函数
    onDrag: function (e) {
        if (!this.dragging) {//如果不做拖拽进行时，直接返回
            return;
        }

		//获取e事件坐标的Point对象
        this.lastTouchPoint = Ext.util.Point.fromEvent(e);
		
		//获取标准化的新坐标偏移对象（即有时候单纯支持横向拖拽或纵向拖拽时只改变其中一个坐标）
        var newOffset = this.getNewOffsetFromTouchPoint(this.lastTouchPoint);

		//若人为地设置了拖拽到边界
        if (this.offsetBoundary != null) {
			//获取坐标在限定范围内的位置，factor为返回的偏移量与边际偏移量的比例
            newOffset = this.offsetBoundary.restrict(newOffset, this.outOfBoundRestrictFactor);
        }

		//设置新坐标位置
        this.setOffset(newOffset);

        this.fireEvent('drag', this, e);



        return true;
    },

	//拖拽结束后促发
    onDragEnd: function (e) {
		//如果处在拖拽进行状态
        if (this.dragging) {
            this.fireEvent('beforedragend', this, e);//触发'beforedragend'事件

			//如果设置了拖拽节点还原属性，且外部没有将拖拽对象的cancelRevert属性设为true
            if (this.revert && !this.cancelRevert) {
                this.setOffset(this.startOffset, true);//拖拽节点回到原来的位置
            } else {
                this.setDragging(false);//结束拖拽状态
            }

            this.fireEvent('dragend', this, e);//触发'dragend'事件
        }



        return true;
    },

	//移动设备转动屏幕时触发
    onOrientationChange: function () {
        this.updateBoundary();//重定节点活动范围
    },

	//设置拖拽过程中拖拽节点的样式
    setDragging: function (dragging) {
		//拖拽中
        if (dragging) {
            if (!this.dragging) {
                this.dragging = true;
                this.getProxyEl().addCls(this.draggingCls);
            }
        } else {
            if (this.dragging) {
                this.dragging = false;
                this.getProxyEl().removeCls(this.draggingCls);
            }
        }

        return this;
    },

	//返回配置对象中混入的代理拖拽节点，如果未指定，则代理拖拽节点就是节点本身
    getProxyEl: function () {
        return this.proxy || this.el;
    },


    destroy: function () {
        this.el.removeCls(this.baseCls);
        this.getProxyEl().removeCls(this.proxyCls);
        this.clearListeners();
        this.disable();
    },


    reset: function () {
        this.startOffset = new Ext.util.Offset(0, 0);
        this.setOffset(this.startOffset);

        var oldInitialRegion = this.initialRegion.copy();

        this.updateBoundary();
        this.initialRegion = this.region = this.getProxyEl().getPageBox(true);
        this.startTouchPoint.x += this.initialRegion.left - oldInitialRegion.left;
        this.startTouchPoint.y += this.initialRegion.top - oldInitialRegion.top;
    },

    //Use this to move the draggable to a coordinate on the screen.
    moveTo: function (x, y) {
        this.setOffset(new Ext.util.Offset(x - this.initialRegion.left, y - this.initialRegion.top));
        return this;
    },


    isDragging: function () {
        return this.dragging;
    },


    isVertical: function () {
        return this.vertical;
    },


    isHorizontal: function () {
        return this.horizontal;
    }
});

//拖拽的动画对象
Ext.util.Draggable.Animation = {};

//拖拽动画对象各方法对象的基类
Ext.util.Draggable.Animation.Abstract = Ext.extend(Object, {

    startTime: null,
    startOffset: 0,
	
    constructor: function (config) {
        config = config || {};

        this.set(config);//配置对象混入

		//对象创建时间
        if (!this.startTime)
            this.startTime = Date.now();
    },

	//设置对象属性
    set: function (name, value) {
        if (Ext.isObject(name)) {
            Ext.apply(this, name);
        }
        else {
            this[name] = value;
        }

        return this;
    },


    getOffset: Ext.emptyFn
});

//获取当前时间断下的偏移量
Ext.util.Draggable.Animation.Linear = Ext.extend(Ext.util.Draggable.Animation.Abstract, {

    duration: 0,


    endOffset: 0,

    getOffset: function () {
        var distance = this.endOffset - this.startOffset,//移动距离
            deltaTime = Date.now() - this.startTime,//当前相对开始的时间距离（因为在外部运行定时器不断调用此对象方法时当前时间会有所变化）
            omegaTime = Math.min(1, (deltaTime / this.duration));//时间比例

        return this.startOffset + (omegaTime * distance);//时间比例对应距离比例，得到的当前时间下的偏移量
    }
});